/*
 *  TCC - Tiny C Compiler
 * 
 *  Copyright (c) 2001-2004 Fabrice Bellard
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
#include "tcc.h"
#include "i386-gen.h"

extern int verbose;

extern int do_debug;

extern int do_bounds_check;

#if !defined(LIBTCC)
extern int do_bench;
#endif

extern int total_lines;
extern int total_bytes;

extern int gnu_ext;

extern int tcc_ext;

static int64_t getclock_us(void)
{
#ifdef _WIN32
	struct _timeb tb;
	_ftime(&tb);
	return (tb.time * 1000LL + tb.millitm) * 1000LL;
#else
	struct timeval tv;
	gettimeofday(&tv, NULL);
	return tv.tv_sec * 1000000LL + tv.tv_usec;
#endif
}

void help(void)
{
	printf(
			"tcc version  0.94 - Tiny C Compiler - Copyright (C) 2001-2006 Fabrice Bellard\n"
				"usage: tcc [-v] [-c] [-o outfile] [-Bdir] [-bench] [-Idir] [-Dsym[=val]] [-Usym]\n"
				"           [-Wwarn] [-g] [-b] [-bt N] [-Ldir] [-llib] [-shared] [-soname name]\n"
				"           [-static] [infile1 infile2...] [-run infile args...]\n"
				"\n"
				"General options:\n"
				"  -v          display current version, increase verbosity\n"
				"  -c          compile only - generate an object file\n"
				"  -o outfile  set output filename\n"
				"  -Bdir       set tcc internal library path\n"
				"  -bench      output compilation statistics\n"
				"  -run        run compiled source\n"
				"  -fflag      set or reset (with 'no-' prefix) 'flag' (see man page)\n"
				"  -Wwarning   set or reset (with 'no-' prefix) 'warning' (see man page)\n"
				"  -w          disable all warnings\n"
				"Preprocessor options:\n"
				"  -E          preprocess only\n"
				"  -Idir       add include path 'dir'\n"
				"  -Dsym[=val] define 'sym' with value 'val'\n"
				"  -Usym       undefine 'sym'\n"
				"Linker options:\n"
				"  -Ldir       add library path 'dir'\n"
				"  -llib       link with dynamic or static library 'lib'\n"
				"  -shared     generate a shared library\n"
				"  -soname     set name for shared library to be used at runtime\n"
				"  -static     static linking\n"
				"  -rdynamic   export all global symbols to dynamic linker\n"
				"  -r          generate (relocatable) object file\n"
				"Debugger options:\n"
				"  -g          generate runtime debug info\n"
#ifdef CONFIG_TCC_BCHECK
			"  -b          compile with built-in memory and bounds checker (implies -g)\n"
#endif
#ifdef CONFIG_TCC_BACKTRACE
			"  -bt N       show N callers in stack traces\n"
#endif
	);
}

#define TCC_OPTION_HAS_ARG 0x0001
#define TCC_OPTION_NOSEP   0x0002 /* cannot have space before option and arg */

typedef struct TCCOption
{
	const char *name;
	uint16_t index;
	uint16_t flags;
} TCCOption;

enum
{
	TCC_OPTION_HELP,
	TCC_OPTION_I,
	TCC_OPTION_D,
	TCC_OPTION_U,
	TCC_OPTION_L,
	TCC_OPTION_B,
	TCC_OPTION_l,
	TCC_OPTION_bench,
	TCC_OPTION_bt,
	TCC_OPTION_b,
	TCC_OPTION_g,
	TCC_OPTION_c,
	TCC_OPTION_static,
	TCC_OPTION_shared,
	TCC_OPTION_soname,
	TCC_OPTION_o,
	TCC_OPTION_r,
	TCC_OPTION_Wl,
	TCC_OPTION_W,
	TCC_OPTION_O,
	TCC_OPTION_m,
	TCC_OPTION_f,
	TCC_OPTION_nostdinc,
	TCC_OPTION_nostdlib,
	TCC_OPTION_print_search_dirs,
	TCC_OPTION_rdynamic,
	TCC_OPTION_run,
	TCC_OPTION_v,
	TCC_OPTION_w,
	TCC_OPTION_pipe,
	TCC_OPTION_E,
};

static const TCCOption tcc_options[] =
{
{ "h", TCC_OPTION_HELP, 0 },
{ "?", TCC_OPTION_HELP, 0 },
{ "I", TCC_OPTION_I, TCC_OPTION_HAS_ARG },
{ "D", TCC_OPTION_D, TCC_OPTION_HAS_ARG },
{ "U", TCC_OPTION_U, TCC_OPTION_HAS_ARG },
{ "L", TCC_OPTION_L, TCC_OPTION_HAS_ARG },
{ "B", TCC_OPTION_B, TCC_OPTION_HAS_ARG },
{ "l", TCC_OPTION_l, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP },
{ "bench", TCC_OPTION_bench, 0 },
{ "bt", TCC_OPTION_bt, TCC_OPTION_HAS_ARG },
#ifdef CONFIG_TCC_BCHECK
		{ "b", TCC_OPTION_b, 0 },
#endif
		{ "g", TCC_OPTION_g, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP },
		{ "c", TCC_OPTION_c, 0 },
		{ "static", TCC_OPTION_static, 0 },
		{ "shared", TCC_OPTION_shared, 0 },
		{ "soname", TCC_OPTION_soname, TCC_OPTION_HAS_ARG },
		{ "o", TCC_OPTION_o, TCC_OPTION_HAS_ARG },
		{ "run", TCC_OPTION_run, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP },
		{ "rdynamic", TCC_OPTION_rdynamic, 0 },
		{ "r", TCC_OPTION_r, 0 },
		{ "Wl,", TCC_OPTION_Wl, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP },
		{ "W", TCC_OPTION_W, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP },
		{ "O", TCC_OPTION_O, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP },
		{ "m", TCC_OPTION_m, TCC_OPTION_HAS_ARG },
		{ "f", TCC_OPTION_f, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP },
		{ "nostdinc", TCC_OPTION_nostdinc, 0 },
		{ "nostdlib", TCC_OPTION_nostdlib, 0 },
		{ "print-search-dirs", TCC_OPTION_print_search_dirs, 0 },
		{ "v", TCC_OPTION_v, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP },
		{ "w", TCC_OPTION_w, 0 },
		{ "pipe", TCC_OPTION_pipe, 0 },
		{ "E", TCC_OPTION_E, 0 },
		{ NULL }, };

/* convert 'str' into an array of space separated strings */
static int expand_args(char ***pargv, const char *str)
{
	const char *s1;
	char **argv, *arg;
	int argc, len;

	argc = 0;
	argv = NULL;
	for (;;)
	{
		while (is_space(*str))
			str++;
		if (*str == '\0')
			break;
		s1 = str;
		while (*str != '\0' && !is_space(*str))
			str++;
		len = str - s1;
		arg = tcc_malloc(len + 1);
		memcpy(arg, s1, len);
		arg[len] = '\0';
		dynarray_add((void ***) &argv, &argc, arg);
	}
	*pargv = argv;
	return argc;
}

static char **files;
static int nb_files, nb_libraries;
static int multiple_files;
static int print_search_dirs;
static int output_type;
static int reloc_output;
static const char *outfile;

int parse_args(TCCState *s, int argc, char **argv)
{
	int optind;
	const TCCOption *popt;
	const char *optarg, *p1, *r1;
	char *r;

	optind = 0;
	while (optind < argc)
	{

		r = argv[optind++];
		if (r[0] != '-' || r[1] == '\0')
		{
			/* add a new file */
			dynarray_add((void ***) &files, &nb_files, r);
			if (!multiple_files)
			{
				optind--;
				/* argv[0] will be this file */
				break;
			}
		}
		else
		{
			/* find option in table (match only the first chars */
			popt = tcc_options;
			for (;;)
			{
				p1 = popt->name;
				if (p1 == NULL)
					error("invalid option -- '%s'", r);
				r1 = r + 1;
				for (;;)
				{
					if (*p1 == '\0')
						goto option_found;
					if (*r1 != *p1)
						break;
					p1++;
					r1++;
				}
				popt++;
			}
			option_found: if (popt->flags & TCC_OPTION_HAS_ARG)
			{
				if (*r1 != '\0' || (popt->flags & TCC_OPTION_NOSEP))
				{
					optarg = r1;
				}
				else
				{
					if (optind >= argc)
						error("argument to '%s' is missing", r);
					optarg = argv[optind++];
				}
			}
			else
			{
				if (*r1 != '\0')
					return 0;
				optarg = NULL;
			}

			switch (popt->index)
			{
			case TCC_OPTION_HELP:
				return 0;

			case TCC_OPTION_I:
				if (tcc_add_include_path(s, optarg) < 0)
					error("too many include paths");
				break;
			case TCC_OPTION_D:
			{
				char *sym, *value;
				sym = (char *) optarg;
				value = strchr(sym, '=');
				if (value)
				{
					*value = '\0';
					value++;
				}
				tcc_define_symbol(s, sym, value);
			}
				break;
			case TCC_OPTION_U:
				tcc_undefine_symbol(s, optarg);
				break;
			case TCC_OPTION_L:
				tcc_add_library_path(s, optarg);
				break;
			case TCC_OPTION_B:
				/* set tcc utilities path (mainly for tcc development) */
				tcc_set_lib_path(s, optarg);
				break;
			case TCC_OPTION_l:
				dynarray_add((void ***) &files, &nb_files, r);
				nb_libraries++;
				break;
			case TCC_OPTION_bench:
				do_bench = 1;
				break;
#ifdef CONFIG_TCC_BACKTRACE
			case TCC_OPTION_bt:
				num_callers = atoi(optarg);
				break;
#endif
#ifdef CONFIG_TCC_BCHECK
			case TCC_OPTION_b:
				do_bounds_check = 1;
				do_debug = 1;
				break;
#endif
			case TCC_OPTION_g:
				do_debug = 1;
				break;
			case TCC_OPTION_c:
				multiple_files = 1;
				output_type = TCC_OUTPUT_OBJ;
				break;
			case TCC_OPTION_static:
				s->static_link = 1;
				break;
			case TCC_OPTION_shared:
				output_type = TCC_OUTPUT_DLL;
				break;
			case TCC_OPTION_soname:
				s->soname = optarg;
				break;
			case TCC_OPTION_o:
				multiple_files = 1;
				outfile = optarg;
				break;
			case TCC_OPTION_r:
				/* generate a .o merging several output files */
				reloc_output = 1;
				output_type = TCC_OUTPUT_OBJ;
				break;
			case TCC_OPTION_nostdinc:
				s->nostdinc = 1;
				break;
			case TCC_OPTION_nostdlib:
				s->nostdlib = 1;
				break;
			case TCC_OPTION_print_search_dirs:
				print_search_dirs = 1;
				break;
			case TCC_OPTION_run:
			{
				int argc1;
				char **argv1;
				argc1 = expand_args(&argv1, optarg);
				if (argc1 > 0)
				{
					parse_args(s, argc1, argv1);
				}
				multiple_files = 0;
				output_type = TCC_OUTPUT_MEMORY;
			}
				break;
			case TCC_OPTION_v:
				do
				{
					if (0 == verbose++)
						printf("tcc version 0.9.24\n");
				} while (*optarg++ == 'v');
				break;
			case TCC_OPTION_f:
				if (tcc_set_flag(s, optarg, 1) < 0 && s->warn_unsupported)
					goto unsupported_option;
				break;
			case TCC_OPTION_W:
				if (tcc_set_warning(s, optarg, 1) < 0 && s->warn_unsupported)
					goto unsupported_option;
				break;
			case TCC_OPTION_w:
				s->warn_none = 1;
				break;
			case TCC_OPTION_rdynamic:
				s->rdynamic = 1;
				break;
			case TCC_OPTION_Wl:
			{
				const char *p;
				if (strstart(optarg, "-Ttext,", &p))
				{
					s->text_addr = strtoul(p, NULL, 16);
					s->has_text_addr = 1;
				}
				else if (strstart(optarg, "--oformat,", &p))
				{
					if (strstart(p, "elf32-", NULL))
					{
						s->output_format = TCC_OUTPUT_FORMAT_ELF;
					}
					else if (!strcmp(p, "binary"))
					{
						s->output_format = TCC_OUTPUT_FORMAT_BINARY;
					}
					else
#ifdef TCC_TARGET_COFF
					if (!strcmp(p, "coff"))
					{
						s->output_format = TCC_OUTPUT_FORMAT_COFF;
					}
					else
#endif
					{
						error("target %s not found", p);
					}
				}
				else
				{
					error("unsupported linker option '%s'", optarg);
				}
			}
				break;
			case TCC_OPTION_E:
				output_type = TCC_OUTPUT_PREPROCESS;
				break;
			default:
				if (s->warn_unsupported)
				{
					unsupported_option: warning("unsupported option '%s'", r);
				}
				break;
			}
		}
	}
	return optind + 1;
}
extern const char *tcc_lib_path;
extern int tok_ident;

int main(int argc, char **argv)
{
	int i;
	TCCState *s;
	int nb_objfiles, ret, optind;
	char objfilename[1024];
	int64_t start_time = 0;

	s = tcc_new();
#ifdef _WIN32
	tcc_set_lib_path_w32(s);
#endif
	output_type = TCC_OUTPUT_EXE;
	outfile = NULL;
	multiple_files = 1;
	files = NULL;
	nb_files = 0;
	nb_libraries = 0;
	reloc_output = 0;
	print_search_dirs = 0;
	ret = 0;

	optind = parse_args(s, argc - 1, argv + 1);
	if (print_search_dirs)
	{
		/* enough for Linux kernel */
		printf("install: %s/\n", tcc_lib_path);
		return 0;
	}
	if (optind == 0 || nb_files == 0)
	{
		if (optind && verbose)
			return 0;
		help();
		return 1;
	}

	nb_objfiles = nb_files - nb_libraries;

	/* if outfile provided without other options, we output an
	 executable */
	if (outfile && output_type == TCC_OUTPUT_MEMORY)
		output_type = TCC_OUTPUT_EXE;

	/* check -c consistency : only single file handled. XXX: checks file type */
	if (output_type == TCC_OUTPUT_OBJ && !reloc_output)
	{
		/* accepts only a single input file */
		if (nb_objfiles != 1)
			error("cannot specify multiple files with -c");
		if (nb_libraries != 0)
			error("cannot specify libraries with -c");
	}

	if (output_type == TCC_OUTPUT_PREPROCESS)
	{
		if (!outfile)
		{
			s->outfile = stdout;
		}
		else
		{
			s->outfile = fopen(outfile, "w");
			if (!s->outfile)
				error("could not open '%s", outfile);
		}
	}
	else if (output_type != TCC_OUTPUT_MEMORY)
	{
		if (!outfile)
		{
			/* compute default outfile name */
			char *ext;
			const char *name = strcmp(files[0], "-") == 0 ? "a" : tcc_basename(
					files[0]);
			pstrcpy(objfilename, sizeof(objfilename), name);
			ext = tcc_fileextension(objfilename);
#ifdef TCC_TARGET_PE
			if (output_type == TCC_OUTPUT_DLL)
			strcpy(ext, ".dll");
			else
			if (output_type == TCC_OUTPUT_EXE)
			strcpy(ext, ".exe");
			else
#endif
			if (output_type == TCC_OUTPUT_OBJ && !reloc_output && *ext)
				strcpy(ext, ".o");
			else
				pstrcpy(objfilename, sizeof(objfilename), "a.out");
			outfile = objfilename;
		}
	}

	if (do_bench)
	{
		start_time = getclock_us();
	}

	tcc_set_output_type(s, output_type);

	/* compile or add each files or library */
	for (i = 0; i < nb_files && ret == 0; i++)
	{
		const char *filename;

		filename = files[i];
		if (output_type == TCC_OUTPUT_PREPROCESS)
		{
			if (tcc_add_file_internal(s, filename, AFF_PRINT_ERROR
					| AFF_PREPROCESS) < 0)
				ret = 1;
		}
		else if (filename[0] == '-' && filename[1])
		{
			if (tcc_add_library(s, filename + 2) < 0)
				error("cannot find %s", filename);
		}
		else
		{
			if (1 == verbose)
				printf("-> %s\n", filename);
			if (tcc_add_file(s, filename) < 0)
				ret = 1;
		}
	}

	/* free all files */
	tcc_free(files);

	if (ret)
		goto the_end;

	if (do_bench)
	{
		double total_time;
		total_time = (double) (getclock_us() - start_time) / 1000000.0;
		if (total_time < 0.001)
			total_time = 0.001;
		if (total_bytes < 1)
			total_bytes = 1;
		printf(
				"%d idents, %d lines, %d bytes, %0.3f s, %d lines/s, %0.1f MB/s\n",
				tok_ident - TOK_IDENT, total_lines, total_bytes, total_time,
				(int) (total_lines / total_time), total_bytes / total_time
						/ 1000000.0);
	}

	if (s->output_type == TCC_OUTPUT_PREPROCESS)
	{
		if (outfile)
			fclose(s->outfile);
	}
	else if (s->output_type == TCC_OUTPUT_MEMORY)
	{
		ret = tcc_run(s, argc - optind, argv + optind);
	}
	else
		ret = tcc_output_file(s, outfile) ? 1 : 0;
	the_end:
	/* XXX: cannot do it with bound checking because of the malloc hooks */
	if (!do_bounds_check)
		tcc_delete(s);

#ifdef MEM_DEBUG
	if (do_bench)
	{
		printf("memory: %d bytes, max = %d bytes\n", mem_cur_size, mem_max_size);
	}
#endif
	return ret;
}

